package br.com.gaspar.framework.visao.jsf;


import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import javax.faces.application.ConfigurableNavigationHandler;
import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.bean.ViewScoped;
import javax.faces.context.FacesContext;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;

import org.apache.log4j.Logger;
import org.primefaces.event.SelectEvent;

import br.com.gaspar.framework.modelo.IBaseBO;
import br.com.gaspar.framework.visao.jsf.util.Modo;
import br.com.gaspar.utils.StringUtil;
import br.com.gaspar.utils.entidade.EntidadeBase;
import br.com.gaspar.utils.exception.BaseException;

/**
 * Classe abstrata para ser utilizada como base para os managed beans de todas
 * aplicações web com JSF
 * 
 * @author gaspar
 * 
 */
public abstract class ManagedBeanBase<T extends EntidadeBase> implements IManagedBeanBase, Serializable {
	
	private static final Logger log = Logger.getLogger(ManagedBeanBase.class);
	/**
	 * 
	 */
	private static final long serialVersionUID = 802189578780092033L;

	protected static final String EDITAR = "editar";
	protected static final String LISTAR = "listar";
	protected static final String CRIAR = "criar";
	protected static final String MOSTRAR = "mostrar";
	protected static final String VISUALIZAR = "visualizar";
	
	protected Boolean exibirExcluir = true;
	
	protected List<T> lista;
	
	//protected PrimeFacesDataModel<T> lista;
	
	protected T entidade;
	
	protected String nomeEntidade = "Entidade";
	
	private Integer totalPaginas;
	
	private Integer pagina;
	
	private Integer quantidadeRegistros;
	
	private Boolean usaViewScope;
	
	protected Boolean modoListar;
	
	protected Boolean modoEditar;
	
	protected Boolean modoVisualizar;
	
	protected Modo modo;
	
	protected String namedQueryLista;
	
	protected Integer resultadoMaximo;

	public Integer getResultadoMaximo() {
		return resultadoMaximo;
	}

	public void setResultadoMaximo(Integer resultadoMaximo) {
		this.resultadoMaximo = resultadoMaximo;
	}

	@SuppressWarnings("unchecked")
	public ManagedBeanBase() {
		if (getClass().getGenericSuperclass() instanceof ParameterizedType) {
			Class<T> clazz = (Class<T>) ((ParameterizedType) getClass()
					.getGenericSuperclass()).getActualTypeArguments()[0];
			nomeEntidade = clazz.getCanonicalName();
			try {
				entidade = clazz.newInstance();
				nomeEntidade = clazz.getSimpleName();
			} catch (InstantiationException e) {
				log.error(e.getMessage(), e);
			} catch (IllegalAccessException e) {
				log.error(e.getMessage(), e);
			}
		}
		
		//controla o uso do escopo de visão
		if(getClass().getAnnotation(ViewScoped.class) != null){
			setUsaViewScope(true);
		}else{
			setUsaViewScope(false);
		}
	}

	/**
	 * M�todo implementado nas classes filha para indicar qual a fachada de
	 * neg�cio utilizada pelo ManagedBean
	 * 
	 * @author gaspar
	 * @return
	 */
	protected abstract IBaseBO<T> getFachada();

	public List<T> getLista() {
		if(this.lista == null)
			setLista(new ArrayList<T>());
		return lista;
	}

	public void setLista(List<T> lista) {
		this.lista = lista;
	}

	public T getEntidade() {
		return entidade;
	}

	public void setEntidade(T entidade) {
		this.entidade = entidade;
	}

	public void iniciar() {
		listar();
	}

	public String editar() {

		try {
			alterarModo(Modo.EDITAR);
			editarAntes();

			/*Long id = entidade.getId();

			this.entidade = (T) getFachada().buscarPorId((Class<T>) entidade.getClass(), id);*/
			editarApos();
		} catch (BaseException e) {
			e.printStackTrace();
		}

		return despachar(EDITAR);
	}

	/**
	 * templated method para ser utilizado antes de editar
	 * 
	 * @author gaspar
	 */
	protected void editarAntes() throws BaseException {
		setExibirExcluir(true);
	}

	/**
	 * templated method para ser utilizado depois de editar
	 * 
	 * @author gaspar
	 */
	protected void editarApos() throws BaseException {

	}
	
	/**
	 * 
	 * @param entidade
	 * @return
	 */
	public String editar(T entidade){
		setEntidade(entidade);
		return editar();
	}

	public String gravar() {
		try {
			alterarModo(Modo.LISTAR);
			gravarAntes();

			if (entidade.getId() == null) {
				getFachada().gravar(entidade);
				
				getLista().add(getEntidade());
			} else {
				getLista().remove(entidade);
				entidade = getFachada().editar(entidade);
				getLista().add(entidade);
			}
			
			gravarApos();

		} catch (BaseException e) {
			log.debug("Tratando erro ao gravar/editar ManagedBean"
					+ e.getDescricao());
			adicionarMensagem(e.getDescricao(), FacesMessage.SEVERITY_ERROR);
			if (entidade.getId() == null) {
				alterarModo(Modo.EDITAR);
				return despachar(EDITAR);
			}
		}

		FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO,
				"Gravado com sucesso!!", null);
		FacesContext.getCurrentInstance().addMessage(null, message);

		return despachar(VISUALIZAR);
	}

	/**
	 * templated method para ser utilizado antes de gravar
	 * 
	 * @author gaspar
	 */
	protected void gravarAntes() throws BaseException {

	}

	/**
	 * templated method para ser utilizado depois de gravar
	 * 
	 * @author gaspar
	 */
	protected void gravarApos() throws BaseException {

	}

	public String criar() {
		try {
			alterarModo(Modo.EDITAR);
			criarAntes();
		} catch (BaseException e) {
			log.debug("Erro ManagedBean - criar " + e.getDescricao());
			adicionarMensagem(getMensagem(e.getDescricao(), "messages"), FacesMessage.SEVERITY_ERROR);
		}

		return despachar(EDITAR);
	}

	protected void criarAntes() throws BaseException {
		setExibirExcluir(false);
	}

	public String listar() {

		try {
			alterarModo(Modo.LISTAR);
			listarAntes();

			// Verifica se a entidade usa exclusão lógica e chama NamedQuery
			// Padrão
			if (getFachada().usaExclusaoLogica(entidade)) {
				setNamedQueryLista("buscarTodosAtivos");
				lista = getFachada().buscarTodosPorNamedQuery(
						getNomeEntidade() + "." + getNamedQueryLista(),
						new Object[] { Boolean.TRUE });
			} else {
				if(StringUtil.ehBrancoOuNulo(getNamedQueryLista())){
					setNamedQueryLista("buscarTodos");
				}
				if(getResultadoMaximo() != null){
					lista = getFachada().buscarTodosPorNamedQuery(getEntidade().getClass().getSimpleName() + "." + getNamedQueryLista(), 1, getResultadoMaximo());
				}else{
					lista = getFachada().buscarTodosPorNamedQuery(getEntidade().getClass().getSimpleName() + "." + getNamedQueryLista());
				}
				
			}

			listarApos();
		} catch (BaseException e) {
			log.debug("Erro ManagedBean - buscarTodos " + e.getDescricao());
			adicionarMensagem(e.getDescricao(), FacesMessage.SEVERITY_ERROR);
		}

		return despachar(LISTAR);
	}
	
	public String navegarPrimeiraPagina() {
    	this.pagina = 1;
    	listarPaginar();
    	return despachar(LISTAR);
    }

    public String navegarPaginaAnterior() {
    	this.pagina = this.pagina - 1;
    	listarPaginar();
    	return despachar(LISTAR);
    }

    public String navegarProximaPagina() {
    	this.pagina = this.pagina + 1;
    	listarPaginar();
    	return despachar(LISTAR);
    }

    public String navegarUltimaPagina() {
    	this.pagina = this.totalPaginas;
    	listarPaginar();
    	return despachar(LISTAR);
    }

    public String alterarQuantidadeRegistros(Integer quantidade) {
    	setPagina(1);
    	setQuantidadeRegistros(quantidade);
    	listarPaginar();
    	return despachar(LISTAR);
    }

    /**
     * Faz a listagem paginada
     * 
     * @return
     * 
     * @author gaspar
     */
    @SuppressWarnings("unchecked")
	public String listarPaginar() {

    	try {
    		String namedQuery = null;
    		Boolean valorTrue = null;
    		// Verifica se a entidade usa exclusão lógica
    		if (getFachada().usaExclusaoLogica(entidade)) {
    			namedQuery = ".buscarTodosAtivos";
    			valorTrue=true;
    		} else {
    			namedQuery = ".buscarTodos";
    		}
    		
    		alterarModo(Modo.LISTAR);

    		listarAntes();
    		
    		if(getQuantidadeRegistros() == null)    	{
               	setQuantidadeRegistros(10);
    		}
    		Integer posBusca = 0;
    		if(getPagina() == null || getPagina() == 0){
    			setPagina(1);
    			posBusca = 1;
    		}else{
    			posBusca = (getPagina().intValue() - 1) * getQuantidadeRegistros();
    			if(posBusca == 0){
    				setPagina(1);
    				posBusca = 1;
    			}
    		}
                
    		float totPg = (float) getFachada().buscarCount((Class<T>) entidade.getClass()) / getQuantidadeRegistros();
    		totalPaginas = (int) ((totPg > (int) totPg || totPg == 0.0) ? totPg + 1 : totPg);
    		lista = getFachada().buscarTodosPorNamedQuery(
    				getEntidade().getClass().getSimpleName() + namedQuery, 
    				posBusca, 
    				getQuantidadeRegistros(),
    				valorTrue);
            
    		listarApos();
    	} catch (BaseException e) {
    		log.debug("Erro ManagedBean - buscarTodos " + e.getDescricao());
    		adicionarMensagem(e.getDescricao(), FacesMessage.SEVERITY_ERROR);
    	}

    	return despachar(LISTAR);
    }

	/**
	 * templated method para ser utilizado antes de buscar todos registros
	 * 
	 * @author gaspar
	 */
	protected void listarAntes() throws BaseException {

	}

	/**
	 * templated method para ser utilizado depois de buscar todos registros
	 * 
	 * @author gaspar
	 */
	protected void listarApos() throws BaseException {

	}

	public String cancelar() {
		listar();
		return despachar(LISTAR);
	}

	public String excluir() {
		if (entidade.getId() != null) {
			try {
				alterarModo(Modo.LISTAR);
				excluirAntes();

				getFachada().excluir(entidade);
				
				excluirApos();

			} catch (BaseException e) {
				log.debug("Erro ManagedBean - excluir " + e.getDescricao());
				adicionarMensagem(e.getDescricao(), FacesMessage.SEVERITY_ERROR);
				
				alterarModo(Modo.EDITAR);
				return despachar(EDITAR);
			}
		}
		FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO,
				"Excluido com sucesso!!", null);
		FacesContext.getCurrentInstance().addMessage(null, message);
		return listar();
	}

	/**
	 * templated method para ser utilizado antes de excluir
	 * 
	 * @author gaspar
	 */
	protected void excluirAntes() throws BaseException {

	}

	/**
	 * templated method para ser utilizado depois de excluir
	 * 
	 * @author gaspar
	 */
	protected void excluirApos() throws BaseException {

	}
	
	/**
	 * 
	 * @param entidade
	 * @return
	 */
	public String excluir(T entidade){
		setEntidade(entidade);
		return excluir();
	}
	
	/**
	 * 
	 * @return
	 */
	public String voltar() {
		//listar();
		alterarModo(Modo.LISTAR);
		return null;
	}

	protected void selecionarAntes() throws BaseException {

	}

	protected void selecionarApos() throws BaseException {

	}

	/**
	 * @return the nomeEntidade
	 */
	public String getNomeEntidade() {
		return nomeEntidade;
	}

	/**
	 * @param nomeEntidade
	 *            the nomeEntidade to set
	 */
	public void setNomeEntidade(String nomeEntidade) {
		this.nomeEntidade = nomeEntidade;
	}

	/**
	 * Coloca uma mensagem no FacesContext como SEVERITY_INFO
	 * 
	 * @param mensagem
	 */
	protected void adicionarMensagem(String mensagem) {
		adicionarMensagem(null, mensagem, FacesMessage.SEVERITY_INFO);
	}

	/**
	 * Coloca uma mensagem no FacesContext como SEVERITY_INFO para um
	 * determinado componente
	 * 
	 * @param idComponente
	 * @param message
	 */
	protected void adicionarMensagem(String idComponente, String message) {
		adicionarMensagem(idComponente, message, FacesMessage.SEVERITY_INFO);
	}

	/**
	 * Coloca uma mensagem no FacesContext informando o nível do SEVERITY
	 * 
	 * @param mensagem
	 * @param severity
	 */
	protected void adicionarMensagem(String mensagem, Severity severity) {
		adicionarMensagem(null, mensagem, severity);
	}

	/**
	 * Coloca uma mensagem no FacesContext informando o nível do SEVERITY e o
	 * id do componente
	 * 
	 * @param idComponente
	 * @param mensagem
	 * @param severity
	 */

	protected void adicionarMensagem(String idComponente, String mensagem,
			Severity severity) {
		FacesContext.getCurrentInstance().addMessage(idComponente,
				new FacesMessage(severity, mensagem, mensagem));
	}

	/**
	 * Coloca uma mensagem (internacionalizada) de informacao no FacesContext
	 * 
	 * @param mensagem
	 */
	protected void adicionarMsgI18NInfo(String mensagem) {
		adicionarMensagem(null, getMensagem(mensagem, "messages"),
				FacesMessage.SEVERITY_INFO);
	}

	/**
	 * Coloca uma mensagem (internacionalizada) de erro no FacesContext
	 * 
	 * @param mensagem
	 */
	protected void adicionarMsgI18NError(String mensagem) {
		adicionarMensagem(null, getMensagem(mensagem, "messages"),
				FacesMessage.SEVERITY_ERROR);
	}

	/**
	 * 
	 * @param chave
	 * @param nomeBundle
	 * @return
	 */
	protected String getMensagem(String chave, String nomeBundle) {
		String mensagem = "";
		ResourceBundle rb = null;
		try {
			rb = ResourceBundle.getBundle(nomeBundle);
			mensagem = rb.getString(chave);

		} catch (Exception e) {
			log.debug("Erro ManagedBean - getMensagem " + e);
		}
		return mensagem;
	}

	/**
	 * 
	 * @param chave
	 * @param nomeBundle
	 * @return
	 */
	protected FacesMessage getFacesMessage(String chave, String nomeBundle) {
		return new FacesMessage(getMensagem(chave, nomeBundle));
	}

	/**
	 * 
	 * @param classeEvento
	 * @param fonte
	 */
	protected void publicarEvento(Class<? extends SystemEvent> classeEvento,
			Object fonte) {
		if (fonte != null) {
			FacesContext ctx = FacesContext.getCurrentInstance();
			ctx.getApplication().publishEvent(ctx, classeEvento, fonte);
		}
	}

	/**
	 * 
	 * @param classeEvento
	 * @param listener
	 */
	protected void registrarParaEvento(
			Class<? extends SystemEvent> classeEvento,
			SystemEventListener listener) {
		FacesContext.getCurrentInstance().getApplication()
				.subscribeToEvent(classeEvento, listener);
	}

	/**
	 * 
	 * @param classeEvento
	 * @param listener
	 */
	protected void retirarRegistroParaEvento(
			Class<? extends SystemEvent> classeEvento,
			SystemEventListener listener) {
		FacesContext.getCurrentInstance().getApplication()
				.unsubscribeFromEvent(classeEvento, listener);
	}

	/*
	 * protected I getFachada() { return bo; }
	 */
	
	public Boolean getExibirExcluir() {
		return exibirExcluir;
	}

	public void setExibirExcluir(Boolean exibirExcluir) {
		this.exibirExcluir = exibirExcluir;
	}
	
	public void selecionarEditar(SelectEvent event) throws BaseException{
		alterarModo(Modo.EDITAR);
		editarAntes();
		FacesContext context = FacesContext.getCurrentInstance();  
        ConfigurableNavigationHandler handler = (ConfigurableNavigationHandler) context.getApplication().getNavigationHandler();
        if(!getUsaViewScope()){
        	handler.performNavigation(EDITAR);
        }
    }
	
	/**
	 * Fluxo de visualização
	 * 
	 * @return
	 * 
	 * @author gaspar
	 */
	public String visualizar(){
		alterarModo(Modo.VISUALIZAR);
		visualizarAntes();
		return despachar(VISUALIZAR);
	}
	
	protected void visualizarAntes(){
		
	}
	
	/**
	 * Fluxo de visualização
	 * 
	 * @param entidade
	 * @return
	 */
	public String visualizar(T entidade){
		setEntidade(entidade);
		return visualizar();
	}
	
	/**
	 * Controla a navegação entre as páginas, se o MB usar ViewScope retorna null
	 * @param destino
	 * @return
	 * 
	 * @author gaspar
	 */
	public String despachar(String destino){
		if(getUsaViewScope()){
			destino = null;
		}
		return destino;
	}
	
	/**
	 * Seta qual o modo de operação
	 * EDITAR, LISTAR ou VISUALIZAR.
	 * @param modo
	 * 
	 * @author gaspar
	 */
	protected void alterarModo(Modo modo){
		this.modo = modo;
		
		switch (getModo()) {
		case LISTAR:
			modoListar = true;
			modoEditar = false;
			modoVisualizar = false;
			break;
			
		case EDITAR:
			modoListar = false;
			modoEditar = true;
			modoVisualizar = false;
			break;
			
		case VISUALIZAR:
			modoListar = false;
			modoEditar = false;
			modoVisualizar = true;
			break;
		}
	}

	public Integer getTotalPaginas() {
		return totalPaginas;
	}

	public void setTotalPaginas(Integer totalPaginas) {
		this.totalPaginas = totalPaginas;
	}

	public Integer getPagina() {
		return pagina;
	}

	public void setPagina(Integer pagina) {
		this.pagina = pagina;
	}

	public Integer getQuantidadeRegistros() {
		return quantidadeRegistros;
	}

	public void setQuantidadeRegistros(Integer quantidadeRegistros) {
		this.quantidadeRegistros = quantidadeRegistros;
	}

	public Boolean getUsaViewScope() {
		return usaViewScope;
	}

	public void setUsaViewScope(Boolean usaViewScope) {
		this.usaViewScope = usaViewScope;
	}

	public Boolean getModoListar() {
		return modoListar;
	}

	public void setModoListar(Boolean modoListar) {
		this.modoListar = modoListar;
	}

	public Boolean getModoEditar() {
		return modoEditar;
	}

	public void setModoEditar(Boolean modoEditar) {
		this.modoEditar = modoEditar;
	}

	public Boolean getModoVisualizar() {
		return modoVisualizar;
	}

	public void setModoVisualizar(Boolean modoVisualizar) {
		this.modoVisualizar = modoVisualizar;
	}

	public Modo getModo() {
		return modo;
	}

	public void setModo(Modo modo) {
		this.modo = modo;
	}

	public String getNamedQueryLista() {
		return namedQueryLista;
	}

	public void setNamedQueryLista(String namedQueryLista) {
		this.namedQueryLista = namedQueryLista;
	}
}
